package com.svconnect.services;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import com.svconnect.annotations.OperationContract;
import com.svconnect.annotations.ServiceContract;
import com.svconnect.exceptions.BindingNotFoundException;
import com.svconnect.exceptions.ConnectionException;
import com.svconnect.exceptions.FaultException;
import com.svconnect.model.config.BindingType;
import com.svconnect.model.config.Endpoint;
import com.svconnect.model.soap.SoapRequestEnvelope;
import com.svconnect.transport.HttpTransport;
import com.svconnect.transport.Transport;
import com.svconnect.util.Log;

public abstract class ServiceBase<T> {
	
	private String endpointName;
	private String endpointAddress;
	
	public ServiceBase() {}
	
	public ServiceBase(String endpointName) {
		this.endpointName = endpointName;
	}
	
	public ServiceBase(String endpointName, String endpointAddress) {
		this.endpointName = endpointName;
		this.endpointAddress = endpointAddress;
	}
	
	public String getEndpointAddress() {
		return endpointAddress;
	}

	public void setEndpointAddress(String endpointAddress) {
		this.endpointAddress = endpointAddress;
	}	
	
	public String getEndpointName() {
		return endpointName;
	}

	public void setEndpointName(String endpointName) {
		this.endpointName = endpointName;
	}

	protected Object invoke(InvocationParamList params) {
		long startTime = System.currentTimeMillis();
		
		String methName = getInvokedMethodName();
		Class<?> ifType = getServiceInterface();
		Method meth = getInterfaceMethod(ifType, methName);

		String realMethName = getMethodName(meth);
		String actionName = getMethodAction(meth);
		String serviceNamespace = getServiceNamespace(ifType);
		Log.i(String.format("Invoking %s (%s)",realMethName, actionName));
		
		int i=0;
		Class<?>[] paramTypes = meth.getParameterTypes();	
		for(InvocationParam p : params) {
			Class<?> pt = paramTypes[i++];
			if (pt.isPrimitive())
				pt = p.getValue().getClass();
			p.setType(pt);
		}
		
		Class<?>[] exceptions = meth.getExceptionTypes();
		Type returnType = meth.getGenericReturnType();
		
		Endpoint endpoint = this.getEndpoint(ifType.getName());
		String endpointAddr = endpoint.getAddress();
		BindingType binding = endpoint.getBinding();
		
		SoapRequestEnvelope reqEnv = new SoapRequestEnvelope(binding);
		reqEnv.setMethodName(realMethName);
		reqEnv.setAction(actionName);
		reqEnv.setServiceUrl(endpointAddr);
		reqEnv.setServiceNamespace(serviceNamespace);
		reqEnv.setParams(params);
		
		try {
			String requestUri = String.format("%s", endpointAddr ,realMethName);
			Transport transport = new HttpTransport(requestUri);
			Object obj = transport.call(reqEnv, returnType, exceptions);
			Log.v(String.format("Invocation time: %d ms", System.currentTimeMillis()-startTime));
			return obj;
		} catch (FaultException e) {
			throw e;
		} catch (Exception e) {
			Log.e(e);
			throw new ConnectionException(e);
		}
	}
	
	private Endpoint getEndpoint(String contractName) {
		Endpoint endpoint = null;
			if (this.getEndpointAddress()!=null) {
				try {
					endpoint = WebConfig.getInstance().getBinding(contractName);
				}
				catch(BindingNotFoundException ex){
					endpoint = new Endpoint();
					endpoint.setName(this.getEndpointName());
				}
				endpoint.setAddress(this.getEndpointAddress());
			}
			else {
				endpoint = WebConfig.getInstance().getBinding(contractName);
			}
		return endpoint;
	}
	
	private Method getInterfaceMethod(Class<?> interfaceType, String methodName) {
		Method[] methods = interfaceType.getMethods();
		for(Method m : methods) {
			if (m.getName().equals(methodName))
				return m;
		}		
		return null;
	}
	
	private Class<?> getServiceInterface() {
		ParameterizedType superclass = (ParameterizedType)getClass().getGenericSuperclass();
		Class<?> interfaceType = (Class<?>) ((ParameterizedType) superclass).getActualTypeArguments()[0];
		return interfaceType;
	}
	
	private String getInvokedMethodName() {
		StackTraceElement[] stElems = Thread.currentThread().getStackTrace();
		StackTraceElement invokedMethod = stElems[3];
		String methodName = invokedMethod.getMethodName();
		return methodName;
	}

	private String getServiceNamespace(Class<?> iface) {
		ServiceContract svcContract = iface.getAnnotation(ServiceContract.class);
		if (svcContract!=null) {
			return svcContract.namespace();
		}
		return null;
	}
	
	private String getMethodName(Method method) {
		OperationContract opContract = method.getAnnotation(OperationContract.class);
		if (opContract!=null) {
			return opContract.name();
		}
		return null;
	}
	
	private String getMethodAction(Method method) {
		OperationContract opContract = method.getAnnotation(OperationContract.class);
		if (opContract!=null) {
			return opContract.action();
		}
		return null;
	}

}
